﻿#include <QFile>
#include <QFileInfo>
#include <QDir>
#include "JZNetFileTrans.h"

JZNetPackFileOperatorReq::JZNetPackFileOperatorReq()
{
    method = Pull;
    taskId = -1;
    localFileSize = 0;
}

JZNetPackFileOperatorReq::~JZNetPackFileOperatorReq()
{
}

int JZNetPackFileOperatorReq::type() const
{
    return NetPack_fileOpReq;
}

void JZNetPackFileOperatorReq::saveToStream(QDataStream& s) const
{
    s << method;
    s << taskId;
    s << localFileSize;
    s << remoteFile;
}

void JZNetPackFileOperatorReq::loadFromStream(QDataStream& s)
{
    s >> method;
    s >> taskId;
    s >> localFileSize;
    s >> remoteFile;
}

//JZNetPackFileOperator
JZNetPackFileOperatorRes::JZNetPackFileOperatorRes() 
{
    remoteTaskId = -1;
    taskId = -1;
    fileSize = 0;
}

JZNetPackFileOperatorRes::~JZNetPackFileOperatorRes() 
{
}

int JZNetPackFileOperatorRes::type() const 
{
    return NetPack_fileOpRes; 
}

void JZNetPackFileOperatorRes::saveToStream(QDataStream& s) const 
{
    s << taskId;
    s << fileSize;
    s << remoteTaskId;
    s << error;
}

void JZNetPackFileOperatorRes::loadFromStream(QDataStream& s) 
{
    s >> taskId;
    s >> fileSize;
    s >> remoteTaskId;
    s >> error;
}

//JZNetPackFileReq
JZNetPackFileReq::JZNetPackFileReq() 
{
    remoteTaskId = -1;
    offset = 0;
}

JZNetPackFileReq::~JZNetPackFileReq() 
{
}

int JZNetPackFileReq::type() const 
{ 
    return NetPack_fileDataReq; 
}

void JZNetPackFileReq::saveToStream(QDataStream& s) const 
{
    Q_ASSERT(remoteTaskId != -1);
    s << remoteTaskId;
    s << offset;
}

void JZNetPackFileReq::loadFromStream(QDataStream& s) 
{
    s >> remoteTaskId;
    s >> offset;
}

//JZNetPackFileRes
JZNetPackFileRes::JZNetPackFileRes() 
{
    remoteTaskId = -1;
    ok = false;
}

JZNetPackFileRes::~JZNetPackFileRes() 
{
}

int JZNetPackFileRes::type() const 
{ 
    return NetPack_fileDataRes;
}

void JZNetPackFileRes::saveToStream(QDataStream& s) const 
{
    Q_ASSERT(remoteTaskId != -1);
    s << remoteTaskId;
    s << ok;
    s << buffer;
}

void JZNetPackFileRes::loadFromStream(QDataStream& s) 
{
    s >> remoteTaskId;
    s >> ok;
    s >> buffer;
}

//JZNetFileTask
JZNetFileTask::JZNetFileTask()
{    
    type = Send;
    netId = -1;
    taskId = -1;
    remoteTaskId = -1;
    error = false;
    fileSize = 0;
    offset = 0;
}

//JZNetFileTrans
bool JZNetFileTrans::isFilePack(int type)
{
    return type >= NetPack_fileOpReq && type <= NetPack_fileDataRes;
}

void JZNetFileTrans::registPack()
{
    JZNetRegistPack<JZNetPackFileOperatorReq>(NetPack_fileOpReq);
    JZNetRegistPack<JZNetPackFileOperatorRes>(NetPack_fileOpRes);
    JZNetRegistPack<JZNetPackFileReq>(NetPack_fileDataReq);
    JZNetRegistPack<JZNetPackFileRes>(NetPack_fileDataRes);
}

JZNetFileTrans::JZNetFileTrans(QObject* parent)
    :QObject(parent)
{
	m_client = nullptr;
	m_server = nullptr;
    m_taskId = 0;
    m_eachRoundSize = 64 * 1024;
}

JZNetFileTrans::~JZNetFileTrans()
{
}

void JZNetFileTrans::setClient(JZNetClient* client)
{
	m_client = client;
}

void JZNetFileTrans::setServer(JZNetServer* server)
{
	m_server = server;
}

void JZNetFileTrans::setDir(QString path)
{
    m_path = path;
}

QString JZNetFileTrans::fullPath(QString path)
{
    if (QDir::isAbsolutePath(path))
        return path;
    else
    {
        Q_ASSERT(!m_path.isEmpty());
        if (path.startsWith("./"))
            return m_path + "/" + path.mid(2);
        else
            return m_path + "/" + path;
    }
}

void JZNetFileTrans::recvPack(int net_id, JZNetPackPtr pack)
{
    if (pack->type() == NetPack_fileOpReq)
    {
        auto req = (JZNetPackFileOperatorReq*)pack.data();
        QString remote_path = fullPath(req->remoteFile);

        JZNetPackFileOperatorRes res;
        JZNetFileTaskPtr task = JZNetFileTaskPtr(new JZNetFileTask());
        task->taskId = m_taskId++;
        task->netId = net_id;
        task->remoteTaskId = req->taskId;
        
        task->filePath = remote_path;
        task->file.setFileName(remote_path);
        bool ret = false;
        if (req->method == JZNetPackFileOperatorReq::Push)
        {
            task->type = JZNetFileTask::Recv;
            ret = task->file.open(QFile::WriteOnly | QFile::Truncate);
            task->fileSize = req->localFileSize;
        }
        else
        {
            task->type = JZNetFileTask::Send;
            ret = task->file.open(QFile::ReadOnly);
            if (ret)
            {
                task->fileSize = task->file.size();
                res.fileSize = task->fileSize;
            }
        }        
        if (ret)
        {
            m_tasks[task->taskId] = task;
            emit sigNewTask(task);
        }
        else
            res.error = "trans failed";

        res.taskId = task->taskId;
        res.remoteTaskId = task->remoteTaskId;
        sendPack(net_id, &res);
        if (ret && task->type == JZNetFileTask::Recv) //对方请求push
            sendFileReq(task.data());
    }
    else
    {
        int task_id = getRemoteTaskId(pack.data());
        if (!m_tasks.contains(task_id))
        {
            qDebug() << "invaild task id:" << task_id;
            return;
        }

        JZNetFileTask *task = m_tasks[task_id].data();
        if (pack->type() == NetPack_fileOpRes)
        {
            auto res = dynamic_cast<JZNetPackFileOperatorRes*>(pack.data());
            task->remoteTaskId = res->taskId;
            if (task->type == JZNetFileTask::Recv)   //我方请求pull
            {
                task->fileSize = res->fileSize;
                sendFileReq(task);
            }
        }
        else if (pack->type() == NetPack_fileDataReq)
        {

            Q_ASSERT(task->remoteTaskId != -1);
            auto req = dynamic_cast<JZNetPackFileReq*>(pack.data());        
            Q_ASSERT(task->type == JZNetFileTask::Send);

            JZNetPackFileRes res;
            res.buffer = task->file.read(m_eachRoundSize);
            task->offset += res.buffer.size();

            bool ok = true;
            if (res.buffer.size() != m_eachRoundSize)
            {
                ok = (task->offset == task->fileSize);
                if(!ok)
                    task->error = true;
            }
            res.remoteTaskId = task->remoteTaskId;
            res.ok = ok;
            sendPack(net_id, &res);

            if (res.buffer.size() != m_eachRoundSize)
                taskFinish(task->taskId);
        }
        else if (pack->type() == NetPack_fileDataRes)
        {
            Q_ASSERT(task->remoteTaskId != -1);
            auto res = dynamic_cast<JZNetPackFileRes*>(pack.data());
            Q_ASSERT(task->type == JZNetFileTask::Recv);
            
            if (res->ok = false)
            {
                task->error = true;
                taskFinish(task->taskId);
            }
            else
            {
                task->file.write(res->buffer);
                task->offset += res->buffer.size();
                if (task->offset != task->fileSize)
                {
                    sendFileReq(task);
                }
                else
                {
                    taskFinish(task->taskId);
                }
            }
        }
    }
}

JZNetFileTaskPtr JZNetFileTrans::startTask(int net_id, JZNetFileTask::Type task_type, QString localUrl, QString remoteUrl)
{
    QString local_path = fullPath(localUrl);

    JZNetFileTaskPtr task = JZNetFileTaskPtr(new JZNetFileTask());
    task->taskId = m_taskId++;
    task->netId = net_id;
    task->type = task_type;
    task->filePath = local_path;
    task->file.setFileName(local_path);

    JZNetPackFileOperatorReq req;
    if (task_type == JZNetFileTask::Send)
    {
        if (!task->file.open(QFile::ReadOnly))
            return JZNetFileTaskPtr();

        task->fileSize = task->file.size();
        req.localFileSize = task->fileSize;
        req.method = JZNetPackFileOperatorReq::Push;
    }
    else
    {
        if (!task->file.open(QFile::WriteOnly | QFile::Truncate))
            return JZNetFileTaskPtr();

        req.method = JZNetPackFileOperatorReq::Pull;
    }
    req.taskId = task->taskId;
    req.remoteFile = remoteUrl;

    m_tasks[task->taskId] = task;
    sendPack(net_id, &req);
    return task;
}

JZNetFileTaskPtr JZNetFileTrans::startPush(int net_id,QString localUrl, QString remoteUrl)
{
    return startTask(net_id, JZNetFileTask::Send, localUrl, remoteUrl);
}

JZNetFileTaskPtr JZNetFileTrans::startPull(int net_id, QString remoteUrl, QString localUrl)
{
    return startTask(net_id, JZNetFileTask::Recv, localUrl, remoteUrl);
}

void JZNetFileTrans::sendPack(int net_id, const JZNetPack* pack)
{
	if (m_client)
		m_client->sendPack(pack);
    else
        m_server->sendPack(net_id, pack);
}

void JZNetFileTrans::taskFinish(int task_id)
{
    auto task = m_tasks[task_id];
    task->file.close();
    emit sigTaskFinish(task);
    m_tasks.remove(task_id);
}

int JZNetFileTrans::getRemoteTaskId(const JZNetPack* pack)
{
    int type = pack->type();
    if (type == NetPack_fileOpRes)
    {
        auto res = dynamic_cast<const JZNetPackFileOperatorRes*>(pack);
        return res->remoteTaskId;
    }
    else if (type == NetPack_fileDataReq)
    {
        auto req = dynamic_cast<const JZNetPackFileReq*>(pack);
        return req->remoteTaskId;
    }
    else if (type == NetPack_fileDataRes)
    {
        auto res = dynamic_cast<const JZNetPackFileRes*>(pack);
        return res->remoteTaskId;
    }
    Q_ASSERT(0);
    return -1;
}

void JZNetFileTrans::sendFileReq(JZNetFileTask* task)
{
    Q_ASSERT(task->type == JZNetFileTask::Recv);

    JZNetPackFileReq req;
    req.offset = task->offset;
    req.remoteTaskId = task->remoteTaskId;
    sendPack(task->netId, &req);
}